package com.stark.commons.spring.core.aspect;

import java.lang.reflect.Method;

import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;

import com.stark.commons.spring.core.annotation.DataSource;
import com.stark.commons.spring.core.context.ApplicationContextUtil;
import com.stark.commons.spring.core.context.DataSourceContextHolder;
import com.stark.commons.spring.core.support.aop.MethodExpressionEvaluator;
import com.stark.commons.spring.core.support.sql.DataSourceLookupKeyProvider;
import com.stark.commons.spring.core.support.sql.DataSourceLookupKeyProvider.NoDataSourceLookupKeyProvider;

import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;

/**
 * 数据源路由切面。
 * <p>所有 {@link com.stark.commons.spring.core.annotation.DataSource @DataSource} 注解的类和方法，
 * 方法执行前切换至注解标注的数据源，方法执行后切回切换前的数据源。
 * @author Ben
 * @since 1.0.0
 * @version 1.0.0
 */
@Aspect
@Order(0)
@AllArgsConstructor
@Slf4j
public class DataSourceRouter {
	
	private final DataSourceLookupKeyProvider LOOKUP_KEY_PROVIDER;
	
	@Around("@within(com.stark.commons.spring.core.annotation.DataSource)||@annotation(com.stark.commons.spring.core.annotation.DataSource)")
	public Object routing(ProceedingJoinPoint joinPoint) throws Throwable {
		Class<? extends Object> clazz = joinPoint.getTarget().getClass();
		Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
		DataSource dataSourceAnnotation = method.getAnnotation(DataSource.class);	// 获取方法上的注解
        if	(dataSourceAnnotation == null) {
        	dataSourceAnnotation = clazz.getAnnotation(DataSource.class);	// 获取类上的注解
        }
        String dataSourceKey = null;
        if (dataSourceAnnotation != null) {
        	dataSourceKey = dataSourceAnnotation.value();
        	if ("".equals(dataSourceKey)) {
        		dataSourceKey = dataSourceAnnotation.dataSource();
        	}
        	
        	if ("".equals(dataSourceKey)) {
        		DataSourceLookupKeyProvider lookupKeyProvider = LOOKUP_KEY_PROVIDER;
        		Class<? extends DataSourceLookupKeyProvider> lookupKeyProviderClass = dataSourceAnnotation.lookupKeyProvider();
        		if (!NoDataSourceLookupKeyProvider.class.equals(lookupKeyProviderClass)) {
        			lookupKeyProvider = ApplicationContextUtil.getBean(lookupKeyProviderClass);
        			if (lookupKeyProvider == null) {
        				lookupKeyProvider = lookupKeyProviderClass.newInstance();
        			}
        		}
        		if (lookupKeyProvider != null) {
    				MethodExpressionEvaluator evaluator = new MethodExpressionEvaluator(joinPoint);
            		String lookupKeyCondition = String.valueOf(evaluator.getValue(dataSourceAnnotation.lookupKeyCondition()));
            		dataSourceKey = lookupKeyProvider.getLookupKey(lookupKeyCondition);
        		}
        	}
        }
        if (StringUtils.isBlank(dataSourceKey) || dataSourceKey.equals(DataSourceContextHolder.currDataSourceKey())) {
        	return joinPoint.proceed();
        }
        
        Object result = null;
        String currDataSourceKey = DataSourceContextHolder.currDataSourceKey();
        DataSourceContextHolder.setDataSourceKey(dataSourceKey);
        log.info("AOP 切换数据源：method={},fromDataSourceKey={},toDataSourceKey={}",
        		clazz.getName() + "#" + method.getName(), currDataSourceKey, dataSourceKey);
        try {
        	result = joinPoint.proceed();
        } finally {
        	currDataSourceKey = DataSourceContextHolder.currDataSourceKey();
        	DataSourceContextHolder.removeDataSourceKey();
        	log.info("AOP 切回数据源：method={},fromDataSourceKey={},toDataSourceKey={}",
            		clazz.getName() + "#" + method.getName(), currDataSourceKey, DataSourceContextHolder.currDataSourceKey());
        }
		return result;
	}
	
}
